﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using Daybreak.Core.SceneGraph;
using Daybreak.Core.Rendering.Materials;
using Daybreak.Core.Rendering.Pipeline;


namespace Daybreak.Core.Rendering
{
    /// <summary>
    /// Simple frame rasterizer, performs Z-sroting (if enabled) to save fillrate
    /// </summary>
    public class Rasterizer
    {
        class ItemList: ICloneable<ItemList>, IResetable
        {
            public ResetableList<RenderQueue.Item> List = new ResetableList<RenderQueue.Item>(8);

            public float Distance;
            public IMaterial Material;

            public ItemList() { }

            public void Sort(Comparison<RenderQueue.Item> comparer)
            {
                if (List.ItemsCount == 0)
                    return;

                if (List.ItemsCount == 1)
                {
                    Distance = List.GetAt(0).SquaredDistance;
                    return;
                }
                    
                List.Sort(comparer);


                int cnt = List.ItemsCount;

                float total = 0;

                for (int i = 0; i < cnt; i++)
                {
                    total += List.GetAt(i).SquaredDistance;
                }

                Distance = total / (float)cnt;

            }

            #region ICloneable<ItemList> Members

            public ItemList Clone()
            {
                return new ItemList();
            }

            #endregion

            #region IResetable Members

            public void Reset()
            {
                List.Reset(false);
            }

            #endregion

            public override string ToString()
            {
                return string.Format("Renderable items: {0} {1}", List.ItemsCount, Distance);
            }
        }

        int batchSize;
        Pipes.FrameContext _frameContext;
        Pipes.ObjectContext _objectContext;
        ResetableQueue<ItemList> _items = new ResetableQueue<ItemList>(64);
        ResetableList<IMaterial> _itemMaterials = new ResetableList<IMaterial>(64);

        public Rasterizer(Pipes.FrameContext frameContext, int primitevesPerBatch) 
        {
            batchSize = primitevesPerBatch;
            _frameContext = frameContext;
            _objectContext = new Pipes.ObjectContext();
        }

        public virtual void DrawFrame(RenderQueue queue, GraphicsDevice device, Camera camera)
        {
            device.Clear(Color.CornflowerBlue);

            if (batchSize != 0)
                queue.Optimize(batchSize);

            CalculateCameraDistance(queue, camera);
            SortQueueByMaterials(queue, camera);
            camera.UpdateFrameChanges(_frameContext);

            for (int i = 0; i < _items.ItemsCount; i++)
            {
                ItemList list = _items.GetAt(i);
                Shader shader = list.Material.Effect;

                if (shader == null)
                    continue;

                shader.CommitFrameChanges();
                shader.CommitMaterialChanges();

                shader.Begin();

                for (int p = 0; p < shader.Effect.CurrentTechnique.Passes.Count; p++)
                {
                    shader.Effect.CurrentTechnique.Passes[p].Begin();

                    DrawItemList(list, shader, device);

                    shader.Effect.CurrentTechnique.Passes[p].End();
                }

                shader.End();
            }

            device.RenderState.FillMode = FillMode.Solid;

        }

        private void CalculateCameraDistance(RenderQueue queue, Camera camera)
        {
            int count = queue.SlotCount;

            for (int i = 0; i < count; i++)
            {
                RenderQueue.Item item = queue.GetSlot(i);

                item.SetSquaredDistance(ref camera.Position);

            }

        }

        private int CompareItem(RenderQueue.Item item1, RenderQueue.Item item2)
        {
            if (item1.SquaredDistance < item2.SquaredDistance)
                return -1;

            if (item1.SquaredDistance > item2.SquaredDistance)
                return 1;

            return 0;
        }

        private int CompareList(ItemList item1, ItemList item2)
        {
            if (item1.Distance < item2.Distance)
                return -1;

            if (item1.Distance > item2.Distance)
                return 1;

            return 0;
        }

        private void DrawItemList(ItemList list, Shader shader, GraphicsDevice device)
        {
            int count = list.List.ItemsCount;

            for (int i = 0; i < count; i++)
            {
                RenderQueue.Item item = list.List.GetAt(i);
                item.SetupInstance(device, _objectContext);

                shader.CommitObjectChanges();

                item.BeforeRender(device);

                shader.Effect.CommitChanges();

                item.DrawSelf(device);

                item.AfterRender(device);

            }
        }

        private void SortQueueByMaterials(RenderQueue queue, Camera camera)
        {
            int count = queue.SlotCount;

            _items.Reset(false);
            _itemMaterials.Reset(false);

            for (int i = 0; i < count; i++)
            {
                RenderQueue.Item item = queue.GetSlot(i);

                int idx = _itemMaterials.IndexOf(item.Material);

                ItemList list = null;

                if (idx < 0)
                {
                    _itemMaterials.Add(item.Material);
                    list = _items.Allocate();
                    list.Material = item.Material;
                }
                else
                {
                    list = _items.GetAt(idx);
                }

                list.List.Add(item);

            }

            if (!Engine.DoZSorting)
                return;

            for (int i = 0; i < _items.ItemsCount; i++)
            {
                _items.GetAt(i).Sort(CompareItem);
            }

            _items.Sort(CompareList);
        }
    }
}
